#include <cstdlib>
#include <iostream>
#include <fstream>
#include <string>
#include <set>
#include <vector>

#include "../../projectevent.h"
#include "../../projecteventbus.h"
#include "../../project_p.h"

#include "msvcprojectcompiler.h"

INIT_PROJECT_COMPILER_PLUGIN_FACTORY( MSVCProjectCompiler )
INIT_PROJECT_COMPILER_PLUGIN_NAME( "MSVC Project Compiler Plugin" )
INIT_PROJECT_COMPILER_PLUGIN_VERSION( -1 )
INIT_PROJECT_COMPILER_PLUGIN_AUTHOR( "The Cine Developer" )

static std::string change_ext( const std::string& filename_, const char* target_ext )
{
    std::string filename = filename_;
    size_t found = filename.find_last_of( '.' );
    if ( found != std::string::npos )
        return filename.replace( found, filename.length()-found, target_ext );
    return filename;
}

void MSVCProjectCompiler::RegisterEvent( ProjectEventBus* eventBus )
{
    eventBus->AttachListener( Events::ProjectBuild, this );
    eventBus->AttachListener( Events::ProjectClean, this );
//     eventBus->AttachListener( EventType_ProjectInstall, this );//TODO
    eventBus->AttachListener( Events::ProjectRebuild, this );
}

bool MSVCProjectCompiler::HandleEvent( Events::Event* event )
{
    /// NOTE: initialize shared d-pointer here
    switch ( event->GetType() ) {
        /// import/export
        case Events::ProjectBuild: {
            d = ((ProjectBuildEvent*)(event))->d;
            build();
            break;
        }
        case Events::ProjectClean: {
            d = ((ProjectCleanEvent*)(event))->d;
            clean();
            break;
        }
        case Events::ProjectRebuild: {
            d = ((ProjectRebuildEvent*)(event))->d;
            rebuild();
            break;
        }
        default:
            std::cerr << "Unexpected Event Here!" << std::endl;
    }
    std::cerr << "catch event from msvc compiler sub plugin" << std::endl;
    return false;
}

bool MSVCProjectCompiler::build()
{
    std::ofstream outfile;
    outfile.open( "cc.bat" );

    std::string basedir = d->project_basedir + '/';

    ProjectTarget target = d->project_targets[0];
    /// compiling
    std::cerr<<"compiling...\n";
    /// prepare
    std::string cflags;
    std::vector<std::string>::const_iterator it2 = target.project_includedirs.begin();
    std::vector<std::string>::const_iterator end2 = target.project_includedirs.end();
    while ( it2 != end2 ) {
        cflags += "/I";
        cflags += *it2;
        cflags += ' ';
        ++it2;
    }
    std::vector<std::string>::const_iterator it3 = d->optflags.defined_marcos.begin();
    std::vector<std::string>::const_iterator end3 = d->optflags.defined_marcos.end();
    while ( it3 != end3 ) {
        cflags += *it3;
        cflags += ' ';
        ++it3;
    }
    /// commands
    std::vector<std::string>::const_iterator it = target.target_files.begin();
    std::vector<std::string>::const_iterator end = target.target_files.end();
    while ( it != end ) {
        std::string cmd = "Z:/home/nihui/bin/cl.exe /nologo /c ";///TODO: cfg
        cmd += cflags;
        cmd += ' ';
        cmd += basedir;
        cmd += *it;
        cmd += " /Fo";
        cmd += basedir;
        cmd += change_ext( *it, ".obj" );
        std::cerr << cmd << std::endl;
        outfile << cmd << std::endl;
//         system( cmd.c_str() );
        ++it;
    }
    
    /// linking
    std::cerr<<"linking...\n";
    std::string ldflags;
    std::vector<std::string>::const_iterator it4 = target.project_linktargets.begin();
    std::vector<std::string>::const_iterator end4 = target.project_linktargets.end();
    while ( it4 != end4 ) {
        ldflags += *it4;
        ldflags += ' ';
        ++it4;
    }
    std::string linkcmd = "Z:/home/nihui/bin/link.exe /nologo ";///TODO: cfg
    linkcmd += ldflags;
    it = target.target_files.begin();
    while ( it != end ) {
        /// replace XYZ.cpp to XYZ.obj
        linkcmd += basedir;
        linkcmd += change_ext( *it, ".obj" );
        linkcmd += ' ';
        ++it;
    }
    if ( !target.target_bin.empty() ) {
        linkcmd += " /OUT:";
        linkcmd += basedir;
        linkcmd += target.target_bin;
    }
    else {
        linkcmd += " /OUT:";
        linkcmd += basedir;
        linkcmd += "main.exe";
    }
    std::cerr << linkcmd << std::endl;
    outfile << linkcmd << std::endl;
//     system( linkcmd.c_str() );

    outfile.close();
    return true;
}

bool MSVCProjectCompiler::clean()
{
    std::ofstream outfile;
    outfile.open( "clean.bat" );
    
    std::string basedir = d->project_basedir + '/';

    ProjectTarget target = d->project_targets[0];
    std::string rmcmd = "cmd.exe /c del ";///TODO: cfg, portable!
    std::vector<std::string>::const_iterator it = target.target_files.begin();
    std::vector<std::string>::const_iterator end = target.target_files.end();
    while ( it != end ) {
        /// replace XYZ.cpp to XYZ.obj
        rmcmd += basedir;
        rmcmd += change_ext( *it, ".obj" );
        rmcmd += ' ';
        ++it;
    }
    std::cerr << rmcmd << std::endl;
    outfile << rmcmd << std::endl;
//     system( rmcmd.c_str() );
    outfile.close();
    return true;
}

bool MSVCProjectCompiler::rebuild()
{
    return clean() && build();
}
